// Windows/FileDir.cpp

#include "StdAfx.h"

#include "FileDir.h"
#include "FileFind.h"
#include "FileName.h"

#include <string.h>

namespace Common {

#if defined(WIN_LONG_PATH)
#define WIN_LONG_PATH2
#endif

// SetCurrentDirectory doesn't support \\?\ prefix

namespace FileSystem {

#ifdef WIN_LONG_PATH
bool GetLongPathBase(LPCTSTR fileName, CString &res);
bool GetLongPath(LPCTSTR fileName, CString &res);
#endif




bool GetWindowsDirectory(CString &path)
{
  UINT needLength = ::GetWindowsDirectory(path.GetBuffer(MAX_PATH + 1), MAX_PATH + 1);
  path.ReleaseBuffer();
  return (needLength > 0 && needLength <= MAX_PATH);
}

bool GetSystemDirectory(CString &path)
{
  UINT needLength = ::GetSystemDirectory(path.GetBuffer(MAX_PATH + 1), MAX_PATH + 1);
  path.ReleaseBuffer();
  return (needLength > 0 && needLength <= MAX_PATH);
}

bool SetDirTime(LPCWSTR fileName, const FILETIME *cTime, const FILETIME *aTime, const FILETIME *mTime)
{
  
  HANDLE hDir = ::CreateFileW(fileName, GENERIC_WRITE,
      FILE_SHARE_READ | FILE_SHARE_WRITE,
      NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
  #ifdef WIN_LONG_PATH
  if (hDir == INVALID_HANDLE_VALUE)
  {
    CString longPath;
    if (GetLongPath(fileName, longPath))
      hDir = ::CreateFileW(longPath, GENERIC_WRITE,
        FILE_SHARE_READ | FILE_SHARE_WRITE,
        NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
  }
  #endif

  bool res = false;
  if (hDir != INVALID_HANDLE_VALUE)
  {
    res = BOOLToBool(::SetFileTime(hDir, cTime, aTime, mTime));
    ::CloseHandle(hDir);
  }
  return res;
}

bool SetFileAttributes(LPCTSTR fileName, DWORD fileAttributes)
{
  if (::SetFileAttributes(fileName, fileAttributes))
    return true;
  #ifdef WIN_LONG_PATH2
  CString longPath;
  if (GetLongPath(fileName, longPath))
    return BOOLToBool(::SetFileAttributes(longPath, fileAttributes));
  #endif
  return false;
}

bool RemoveDirectory(LPCTSTR pathName)
{
  if (::RemoveDirectory(pathName))
    return true;
  #ifdef WIN_LONG_PATH2
  CString longPath;
  if (GetLongPath(pathName, longPath))
    return BOOLToBool(::RemoveDirectory(longPath));
  #endif
  return false;
}

#ifdef WIN_LONG_PATH
bool GetLongPaths(LPCTSTR s1, LPCTSTR s2, CString &d1, CString &d2)
{
  if (!GetLongPathBase(s1, d1) || !GetLongPathBase(s2, d2))
    return false;
  if (d1.IsEmpty() && d2.IsEmpty()) return false;
  if (d1.IsEmpty()) d1 = s1;
  if (d2.IsEmpty()) d2 = s2;
  return true;
}
#endif

bool MoveFile(LPCTSTR existFileName, LPCTSTR newFileName)
{
  if (::MoveFile(existFileName, newFileName))
    return true;
  #ifdef WIN_LONG_PATH2
  CString d1, d2;
  if (GetLongPaths(existFileName, newFileName, d1, d2))
    return BOOLToBool(::MoveFile(d1, d2));
  #endif
  return false;
}


bool CreateDirectory(LPCTSTR pathName)
{
  if (::CreateDirectory(pathName, NULL))
    return true;
  #ifdef WIN_LONG_PATH2
  if (::GetLastError() != ERROR_ALREADY_EXISTS)
  {
    CString longPath;
    if (GetLongPath(pathName, longPath))
      return BOOLToBool(::CreateDirectory(longPath, NULL));
  }
  #endif
  return false;
}

bool CreateComplexDirectory(LPCTSTR _aPathName)
{
  CString pathName = _aPathName;
  int pos = pathName.ReverseFind(CHAR_PATH_SEPARATOR);
  if (pos > 0 && pos == pathName.GetLength() - 1)
  {
    if (pathName.GetLength() == 3 && pathName[1] == ':')
      return true; // Disk folder;
    pathName.Delete(pos);
  }
  CString pathName2 = pathName;
  pos = pathName.GetLength();
  for (;;)
  {
    if (CreateDirectory(pathName))
      break;
    if (::GetLastError() == ERROR_ALREADY_EXISTS)
    {
      File::CFileInfo fileInfo;
      if (!fileInfo.Find(pathName)) // For network folders
        return true;
      if (!fileInfo.IsDir())
        return false;
      break;
    }
    pos = pathName.ReverseFind(CHAR_PATH_SEPARATOR);
    if (pos < 0 || pos == 0)
      return false;
    if (pathName[pos - 1] == ':')
      return false;
    pathName = pathName.Left(pos);
  }
  pathName = pathName2;
  while (pos < pathName.GetLength())
  {
    pos = pathName.Find(CHAR_PATH_SEPARATOR, pos + 1);
    if (pos < 0)
      pos = pathName.GetLength();
    if (!CreateDirectory(pathName.Left(pos)))
      return false;
  }
  return true;
}

bool DeleteFileAlways(LPCTSTR name)
{
  if (!SetFileAttributes(name, 0))
    return false;
  if (::DeleteFile(name))
    return true;
  #ifdef WIN_LONG_PATH2
  CString longPath;
  if (GetLongPath(name, longPath))
    return BOOLToBool(::DeleteFile(longPath));
  #endif
  return false;
}


static bool RemoveDirectorySubItems2(const CString pathPrefix, const File::CFileInfo &fileInfo)
{
  if (fileInfo.IsDir())
    return RemoveDirectoryWithSubItems(pathPrefix + fileInfo.Name);
  return DeleteFileAlways(pathPrefix + fileInfo.Name);
}

bool RemoveDirectoryWithSubItems(const CString &path)
{
  File::CFileInfo fileInfo;
  CString pathPrefix = path + FileName::kDirDelimiter;
  {
    Find::CEnumerator enumerator(pathPrefix + TCHAR(FileName::kAnyStringWildcard));
    while (enumerator.Next(&fileInfo))
      if (!RemoveDirectorySubItems2(pathPrefix, fileInfo))
        return false;
  }
  if (!SetFileAttributes(path, 0))
    return false;
  return RemoveDirectory(path);
}

bool GetOnlyDirPrefix(LPCTSTR fileName, CString &resultName)
{
  int index;
  if (!GetFullPathName(fileName, resultName, index))
    return false;
  resultName = resultName.Left(index);
  return true;
}

bool GetOnlyName(LPCTSTR fileName, CString &resultName)
{
  int index;
  if (!GetFullPathName(fileName, resultName, index))
    return false;
  resultName = resultName.Mid(index);
  return true;
}


bool GetShortPathName(LPCTSTR longPath, CString &shortPath)
{
  DWORD needLength = ::GetShortPathName(longPath, shortPath.GetBuffer(MAX_PATH + 1), MAX_PATH + 1);
  shortPath.ReleaseBuffer();
  return (needLength > 0 && needLength < MAX_PATH);
}

#ifdef WIN_LONG_PATH

static CString GetLastPart(LPCWSTR path)
{
  int i = (int)wcslen(path);
  for (; i > 0; i--)
  {
    WCHAR c = path[i - 1];
    if (c == CHAR_PATH_SEPARATOR || c == '/')
      break;
  }
  return path + i;
}

static void AddTrailingDots(LPCWSTR oldPath, CString &newPath)
{
  int len = (int)wcslen(oldPath);
  int i;
  for (i = len; i > 0 && oldPath[i - 1] == '.'; i--);
  if (i == 0 || i == len)
    return;
  CString oldName = GetLastPart(oldPath);
  CString newName = GetLastPart(newPath);
  int nonDotsLen = oldName.GetLength() - (len - i);
  if (nonDotsLen == 0 || newName.CompareNoCase(oldName.Left(nonDotsLen)) != 0)
    return;
  for (; i != len; i++)
    newPath += '.';
}

#endif

bool GetFullPathName(LPCTSTR fileName, CString &resultPath, int &fileNamePartStartIndex)
{
  resultPath.Empty();
  LPTSTR fileNamePointer = 0;
  LPTSTR buffer = resultPath.GetBuffer(MAX_PATH);
  DWORD needLength = ::GetFullPathName(fileName, MAX_PATH + 1, buffer, &fileNamePointer);
  resultPath.ReleaseBuffer();
  if (needLength == 0)
    return false;
  if (needLength >= MAX_PATH)
  {
    #ifdef WIN_LONG_PATH2
    needLength++;
    buffer = resultPath.GetBuffer(needLength + 1);
    DWORD needLength2 = ::GetFullPathName(fileName, needLength, buffer, &fileNamePointer);
    resultPath.ReleaseBuffer();
    if (needLength2 == 0 || needLength2 > needLength)
    #endif
      return false;
  }
  if (fileNamePointer == 0)
    fileNamePartStartIndex = lstrlen(fileName);
  else
    fileNamePartStartIndex = (int)(fileNamePointer - buffer);
  #ifdef WIN_LONG_PATH
  AddTrailingDots(fileName, resultPath);
  #endif
  return true;
}


bool GetFullPathName(LPCTSTR fileName, CString &path)
{
  int index;
  return GetFullPathName(fileName, path, index);
}


bool GetCurrentDirectory(CString &path)
{
  DWORD needLength = ::GetCurrentDirectory(MAX_PATH + 1, path.GetBuffer(MAX_PATH + 1));
  path.ReleaseBuffer();
  return (needLength > 0 && needLength <= MAX_PATH);
}

bool SearchPath(LPCTSTR path, LPCTSTR fileName, LPCTSTR extension,
  CString &resultPath, UINT32 &filePart)
{
  LPTSTR filePartPointer;
  DWORD value = ::SearchPath(path, fileName, extension,
    MAX_PATH, resultPath.GetBuffer(MAX_PATH + 1), &filePartPointer);
  filePart = (UINT32)(filePartPointer - (LPCTSTR)resultPath);
  resultPath.ReleaseBuffer();
  return (value > 0 && value <= MAX_PATH);
}

bool GetTempPath(CString &path)
{
  DWORD needLength = ::GetTempPath(MAX_PATH + 1, path.GetBuffer(MAX_PATH + 1));
  path.ReleaseBuffer();
  return (needLength > 0 && needLength <= MAX_PATH);
}


UINT GetTempFileName(LPCTSTR dirPath, LPCTSTR prefix, CString &path)
{
  UINT number = ::GetTempFileName(dirPath, prefix, 0, path.GetBuffer(MAX_PATH + 1));
  path.ReleaseBuffer();
  return number;
}


UINT CTempFile::Create(LPCTSTR dirPath, LPCTSTR prefix, CString &resultPath)
{
  Remove();
  UINT number = GetTempFileName(dirPath, prefix, resultPath);
  if (number != 0)
  {
    _fileName = resultPath;
    _mustBeDeleted = true;
  }
  return number;
}

bool CTempFile::Create(LPCTSTR prefix, CString &resultPath)
{
  CString tempPath;
  if (!GetTempPath(tempPath))
    return false;
  if (Create(tempPath, prefix, resultPath) != 0)
    return true;
  if (!GetWindowsDirectory(tempPath))
    return false;
  return (Create(tempPath, prefix, resultPath) != 0);
}

bool CTempFile::Remove()
{
  if (!_mustBeDeleted)
    return true;
  _mustBeDeleted = !DeleteFileAlways(_fileName);
  return !_mustBeDeleted;
}


bool CreateTempDirectory(LPCTSTR prefix, CString &dirName)
{
  
  for (;;)
  {
    {
      CTempFile tempFile;
      if (!tempFile.Create(prefix, dirName))
        return false;
      if (!tempFile.Remove())
        return false;
    }
    if (Find::DoesFileOrDirExist(dirName))
      continue;
    if (FileSystem::CreateDirectory(dirName))
      return true;
    if (::GetLastError() != ERROR_ALREADY_EXISTS)
      return false;
  }
}

bool CTempDirectory::Create(LPCTSTR prefix)
{
  Remove();
  return (_mustBeDeleted = CreateTempDirectory(prefix, _tempDir));
}


}}
